/* SPDX-License-Identifier: MIT-0 */

/* Implements the LogControl1 interface as per specification:
 * https://www.freedesktop.org/software/systemd/man/org.freedesktop.LogControl1.html
 *
 * Compile with 'cc logcontrol-example.c $(pkg-config --libs --cflags libsystemd)'
 *
 * To get and set properties via busctl:
 *
 * $ busctl --user get-property org.freedesktop.Example \
 *                              /org/freedesktop/LogControl1 \
 *                              org.freedesktop.LogControl1 \
 *                              SyslogIdentifier
 *   s "example"
 * $ busctl --user get-property org.freedesktop.Example \
 *                              /org/freedesktop/LogControl1 \
 *                              org.freedesktop.LogControl1 \
 *                              LogTarget
 *   s "journal"
 * $ busctl --user get-property org.freedesktop.Example \
 *                              /org/freedesktop/LogControl1 \
 *                              org.freedesktop.LogControl1 \
 *                              LogLevel
 *   s "info"
 * $ busctl --user set-property org.freedesktop.Example \
 *                              /org/freedesktop/LogControl1 \
 *                              org.freedesktop.LogControl1 \
 *                              LogLevel \
 *                              "s" debug
 * $ busctl --user get-property org.freedesktop.Example \
 *                              /org/freedesktop/LogControl1 \
 *                              org.freedesktop.LogControl1 \
 *                              LogLevel
 *   s "debug"
 */

#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <syslog.h>
#include <systemd/sd-bus.h>
#include <systemd/sd-journal.h>

#define _cleanup_(f) __attribute__((cleanup(f)))

static int log_error(int log_level, int error, const char *str) {
  sd_journal_print(log_level, "%s failed: %s", str, strerror(-error));
  return error;
}

typedef enum LogTarget {
  LOG_TARGET_JOURNAL,
  LOG_TARGET_KMSG,
  LOG_TARGET_SYSLOG,
  LOG_TARGET_CONSOLE,
  _LOG_TARGET_MAX,
} LogTarget;

static const char* const log_target_table[_LOG_TARGET_MAX] = {
  [LOG_TARGET_JOURNAL] = "journal",
  [LOG_TARGET_KMSG]    = "kmsg",
  [LOG_TARGET_SYSLOG]  = "syslog",
  [LOG_TARGET_CONSOLE] = "console",
};

static const char* const log_level_table[LOG_DEBUG + 1] = {
  [LOG_EMERG]   = "emerg",
  [LOG_ALERT]   = "alert",
  [LOG_CRIT]    = "crit",
  [LOG_ERR]     = "err",
  [LOG_WARNING] = "warning",
  [LOG_NOTICE]  = "notice",
  [LOG_INFO]    = "info",
  [LOG_DEBUG]   = "debug",
};

typedef struct object {
  const char *syslog_identifier;
  LogTarget log_target;
  int log_level;
} object;

int main(int argc, char **argv) {
  /* The bus should be relinquished before the program terminates. The cleanup
   * attribute allows us to do it nicely and cleanly whenever we exit the
   * block.
   */
  _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;

  object o = {
    .log_level = LOG_INFO,
    .log_target = LOG_TARGET_JOURNAL,
    .syslog_identifier = "example",
  };
  int r;

  _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
  const char *contents = NULL;
  _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
  char type;

  /* https://man7.org/linux/man-pages/man3/setlogmask.3.html
   * Programs using syslog() instead of sd_journal can use this API to cut logs
   * emission at the source.
   */
  setlogmask(LOG_UPTO(o.log_level));

  r = sd_bus_open_system(&bus);
  if (r < 0)
    return log_error(o.log_level, r, "Failed to sd_bus_open_system: %m");
  
  (void) sd_bus_set_description(bus, "hello_client");

  r = sd_bus_call_method(bus, 
      "org.freedesktop.Example", 
      "/org/freedesktop/LogControl1",
      "org.freedesktop.DBus.Properties", 
      "Get",
      &error, 
      &reply, 
      "ss", 
      "org.freedesktop.LogControl1", 
      "SyslogIdentifier");  

  if (r < 0) {
    printf("Failed to get property on interface\n");
    return -1;
  }

  r = sd_bus_message_peek_type(reply, &type, &contents);
  if (r < 0)
  {
    printf("parse peek type error...\n");
    return -1;
  }

  printf("type: %c\n", type);
  fflush(stdout);

  r = sd_bus_message_enter_container(reply, 'v', contents);
  if (r < 0)
  {
    printf("get sd_bus_message_enter_container error...\n");
    return -1;
  }

  printf("contents: %s\n", contents);
  fflush(stdout);

  r = sd_bus_message_peek_type(reply, &type, &contents);
  if (r < 0)
  {
    printf("parse peek type error 11...\n");
    return -1;
  }  

  printf("type: %c\n", type);

  union {
          uint8_t u8;
          uint16_t u16;
          int16_t s16;
          uint32_t u32;
          int32_t s32;
          uint64_t u64;
          int64_t s64;
          double d64;
          const char *string;
          int i;
  } basic;
  r = sd_bus_message_read_basic(reply, type, &basic);
  if (r < 0)
  {
    printf("read basic error.\n");
    return r;
  }
  switch (type) {
    case SD_BUS_TYPE_STRING:
      printf("basic.string: %s\n", basic.string);
    break;
  }

  r = sd_bus_message_exit_container(reply);
  if (r < 0)
  {
    printf("get sd_bus_message_exit_container error...\n");
    return -1;
  }

  return 0;
}
